Add 'did you mean...' message when a subcommand is not found.
authorPaul Woolcock <paul@woolcock.us>
Thu, 6 Nov 2014 02:05:01 +0000 (21:05 -0500)
committerPaul Woolcock <paul@woolcock.us>
Thu, 6 Nov 2014 14:23:44 +0000 (09:23 -0500)
src/bin/cargo.rs
tests/test_cargo.rs

index d0bdc10a9652fa34769c384ee3588604d354395c..d46b85a7a3723ef889d33516fd7612252372a8ad 100644 (file)
@@ -133,11 +133,31 @@ fn execute(flags: Flags, shell: &mut MultiShell) -> CliResult<Option<()>> {
     Ok(None)
 }
 
+fn find_closest(cmd: &str) -> Option<String> {
+    match list_commands().iter()
+                            // doing it this way (instead of just .min_by(|c| c.lev_distance(cmd)))
+                            // allows us to only make suggestions that have an edit distance of
+                            // 3 or less
+                            .map(|c| (c.lev_distance(cmd), c))
+                            .filter(|&(d, _): &(uint, &String)| d < 4u)
+                            .min_by(|&(d, _)| d) {
+        Some((_, c)) => {
+            Some(c.to_string())
+        },
+        None => None
+    }
+}
+
 fn execute_subcommand(cmd: &str, args: &[String], shell: &mut MultiShell) {
     let command = match find_command(cmd) {
         Some(command) => command,
-        None => return handle_error(CliError::new("No such subcommand", 127),
-                                    shell)
+        None => {
+            let msg = match find_closest(cmd) {
+                Some(closest) => format!("No such subcommand\n\n\tDid you mean ``{}''?\n", closest),
+                None => "No such subcommand".to_string()
+            };
+            return handle_error(CliError::new(msg, 127), shell)
+        }
     };
     let status = Command::new(command)
                          .args(args)
index 6db2f77fa48e597ab24dc94da927f280529b48a8..d70948ccb7d0bc2e089b8ae0ecbd9fdc7ad47c6c 100644 (file)
@@ -5,7 +5,8 @@ use std::str;
 use cargo::util::process;
 
 use support::paths;
-use support::{project, cargo_dir, mkdir_recursive, ProjectBuilder, ResultTest};
+use support::{execs, project, cargo_dir, mkdir_recursive, ProjectBuilder, ResultTest};
+use hamcrest::{assert_that};
 
 fn setup() {
 }
@@ -44,3 +45,30 @@ test!(list_commands_looks_at_path {
     let output = str::from_utf8(output.output.as_slice()).assert();
     assert!(output.contains("\n    1\n"), "missing 1: {}", output);
 })
+
+test!(find_closest_biuld_to_build {
+    let pr = process(cargo_dir().join("cargo"))
+                    .arg("biuld").cwd(paths::root())
+                    .env("HOME", Some(paths::home()));
+
+    assert_that(pr,
+                execs().with_status(127)
+                       .with_stderr("No such subcommand
+
+Did you mean ``build''?
+
+"));
+})
+
+// if a subcommand is more than 3 edit distance away, we don't make a suggestion
+test!(find_closest_dont_correct_nonsense {
+    let pr = process(cargo_dir().join("cargo"))
+                    .arg("asdf").cwd(paths::root())
+                    .env("HOME", Some(paths::home()));
+
+    assert_that(pr,
+                execs().with_status(127)
+                       .with_stderr("No such subcommand
+"));
+})
+